/**
 * @Date: Mar 3, 2010 2:27:45 PM
 */
package com.philip.journal.core.dao;

import java.util.Date;
import java.util.List;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.philip.core.WarningType;
import com.philip.journal.core.Messages;
import com.philip.journal.core.bean.AbstractAuditableBean;
import com.philip.journal.core.bean.AbstractBean;
import com.philip.journal.core.dao.spring.hibernate.BaseDAOSpringHibernate;
import com.philip.journal.core.exception.JournalException;

/**
 * @param <E> - AbstractBean to which this DAO subclass will act.
 * 
 * @author cry30
 */
public abstract class BaseDAOImpl<E extends AbstractBean> extends
        BaseDAOSpringHibernate<E> { // NOPMD by r39 on 3/30/11 12:03 PM

    /**
     * This field is for subclass use. Will need to define class level logger to
     * trace this class.
     */
    private final Log logger = LogFactory.getLog(getClass()); // NOPMD by r39 on 3/30/11 12:02 PM

    /**
     * Factory method used by unit test.
     * 
     * @param <T> AbstractBean type.
     * @param targetClass explicit choice for sub class instance of this
     *            abstract class.
     * @return sub class instance of this class.
     */
    @SuppressWarnings({
            WarningType.UNCHECKED,
            WarningType.RAW })
    public static <T extends AbstractBean> BaseDAOImpl<T> newInstance(
            final Class<T> targetClass)
    {
        return (BaseDAOImpl) new BaseDAOImpl<T>() {}.setType(targetClass);
    }

    /**
     * @param entityId - the primary key value to match with the domain object.
     * @return the domain object matching the specified id.
     * @throws JournalException thrown when the current target is not supported
     *             by this method. Non-supported are methods that do not have
     *             the method getPrimaryKeyField. You may use the
     *             readByProperty(String, String)
     */
    protected E read(final long entityId)
    {
        try {
            final Class<?> klass = getTargetClass();
            final Object object = klass.getConstructor(new Class[0])
                    .newInstance(new Object[0]);
            final String pkeyField = (String) klass.getMethod(
                    "getPrimaryKeyField", new Class[0]).invoke(object,
                    new Object[0]);
            return readObject(pkeyField, entityId);
        } catch (final Exception e) {
            throw new JournalException(
                    "This is not supported for this type of target.  Use readByProperty.",
                    e);
        }
    }

    /**
     * Persists the given object into the database.
     * 
     * If the object passed is an instance of AuditableBean, the corresponding
     * Create/Update Date/Time is also populated by the current system Date.
     * Insert or Update is determined by the given primary key field being empty
     * as insert else it is considered as an update.
     * 
     * @param object - the entity to persist.
     * @throws JournalException
     *             {@link com.philip.journal.core.dao.spring.hibernate.BaseDAOSpringHibernate#save(com.philip.journal.home.bean.Entry)}
     * 
     * @exception IllegalArgumentException when the object passed is null.
     */

    @Override
    public final void save(final E object)
    {
        if (object == null) {
            throw JournalException
                    .wrapperException(new IllegalArgumentException(
                            Messages.Error.IAE_NULL));
        }

        if (object instanceof AbstractAuditableBean) {
            final Date now = new Date();
            final AbstractAuditableBean auditableBean = (AbstractAuditableBean) object;
            final String pkey = auditableBean.getPrimaryKeyField();

            try {
                final Object idvalue = BeanUtils.getProperty(object, pkey);
                if (idvalue == null || "0".equals(idvalue)) {
                    BeanUtils.setProperty(object,
                            AbstractAuditableBean.CREATE_DATE, now);
                    BeanUtils.setProperty(object,
                            AbstractAuditableBean.CREATE_TIME, now);
                }
                BeanUtils.setProperty(object,
                        AbstractAuditableBean.UPDATE_DATE, now);
                BeanUtils.setProperty(object,
                        AbstractAuditableBean.UPDATE_TIME, now);
            } catch (final Exception e) {
                throw new JournalException("System Error", e);
            }
        }
        super.save(object);
    }

    //Should re design for extension.

    /**
     * Delegate method. We do not want subclass to directly use the internal (0
     * suffixed) methods.
     * 
     * @param property entity column.
     * @param value entity column value.
     * @return List of All matched Entities.
     */
    protected List<E> readAll(final String property, final Object value)
    {
        return super.readAll0(property, value);
    }

    /**
     * Delegate method. We do not want subclass to directly use the internal (0
     * suffixed) methods.
     * 
     * @return List of All Entities.
     */
    public List<E> readAll()
    {
        return super.readAll0();
    }

    /**
     * Delegate method. We do not want subclass to directly use the internal (0
     * suffixed) methods.
     * 
     * @param sortCriteria Sort criteria.
     * @return List of All matched Entities sorted.
     */
    protected List<E> readAll(final String[] sortCriteria)
    {
        return super.readAll0(sortCriteria);
    }

    /**
     * Accessor method.
     * 
     * @return the logger
     */
    @Override
    public final Log getLogger()
    {
        return logger;
    }
}
